/*
 * winmisc.c: miscellaneous Windows-specific things
 */

#include <stdio.h>
#include <stdlib.h>
#include "putty.h"
#ifndef SECURITY_WIN32
#define SECURITY_WIN32
#endif
#include <security.h>

OSVERSIONINFO osVersion;

char *platform_get_x_display(void)
{
  /* We may as well check for DISPLAY in case it's useful. */
  return dupstr(getenv("DISPLAY"));
}

Filename *filename_from_str(const char *str)
{
  Filename *ret = snew(Filename);
  ret->path = dupstr(str);
  return ret;
}

Filename *filename_copy(const Filename *fn)
{
  return filename_from_str(fn->path);
}

const char *filename_to_str(const Filename *fn)
{
  return fn->path;
}

int filename_equal(const Filename *f1, const Filename *f2)
{
  return !strcmp(f1->path, f2->path);
}

int filename_is_null(const Filename *fn)
{
  return !*fn->path;
}

void filename_free(Filename *fn)
{
  sfree(fn->path);
  sfree(fn);
}

int filename_serialise(const Filename *f, void *vdata)
{
  char *data = (char *)vdata;
  int len = strlen(f->path) + 1; /* include trailing NUL */
  if (data) {
    strcpy(data, f->path);
  }
  return len;
}
Filename *filename_deserialise(void *vdata, int maxsize, int *used)
{
  char *data = (char *)vdata;
  char *end;
  end = memchr(data, '\0', maxsize);
  if (!end)
    return NULL;
  end++;
  *used = end - data;
  return filename_from_str(data);
}

char filename_char_sanitise(char c)
{
  if (strchr("<>:\"/\\|?*", c))
    return '.';
  return c;
}

#ifndef NO_SECUREZEROMEMORY
/*
 * Windows implementation of smemclr (see misc.c) using SecureZeroMemory.
 */
void smemclr(void *b, size_t n)
{
  if (b && n > 0)
    SecureZeroMemory(b, n);
}
#endif

char *get_username(void)
{
  DWORD namelen;
  char *user;
  int got_username = FALSE;
  DECL_WINDOWS_FUNCTION(
      static, BOOLEAN, GetUserNameExA, (EXTENDED_NAME_FORMAT, LPSTR, PULONG));

  {
    static int tried_usernameex = FALSE;
    if (!tried_usernameex) {
      /* Not available on Win9x, so load dynamically */
      HMODULE secur32 = load_system32_dll("secur32.dll");
      /* If MIT Kerberos is installed, the following call to
         GET_WINDOWS_FUNCTION makes Windows implicitly load
         sspicli.dll WITHOUT proper path sanitizing, so better
         load it properly before */
      HMODULE sspicli = load_system32_dll("sspicli.dll");
      GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
      tried_usernameex = TRUE;
    }
  }

  if (p_GetUserNameExA) {
    /*
     * If available, use the principal -- this avoids the problem
     * that the local username is case-insensitive but Kerberos
     * usernames are case-sensitive.
     */

    /* Get the length */
    namelen = 0;
    (void)p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);

    user = snewn(namelen, char);
    got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
    if (got_username) {
      char *p = strchr(user, '@');
      if (p)
        *p = 0;
    } else {
      sfree(user);
    }
  }

  if (!got_username) {
    /* Fall back to local user name */
    namelen = 0;
    if (GetUserName(NULL, &namelen) == FALSE) {
      /*
       * Apparently this doesn't work at least on Windows XP SP2.
       * Thus assume a maximum of 256. It will fail again if it
       * doesn't fit.
       */
      namelen = 256;
    }

    user = snewn(namelen, char);
    got_username = GetUserName(user, &namelen);
    if (!got_username) {
      sfree(user);
    }
  }

  return got_username ? user : NULL;
}

void dll_hijacking_protection(void)
{
  /*
   * If the OS provides it, call SetDefaultDllDirectories() to
   * prevent DLLs from being loaded from the directory containing
   * our own binary, and instead only load from system32.
   *
   * This is a protection against hijacking attacks, if someone runs
   * PuTTY directly from their web browser's download directory
   * having previously been enticed into clicking on an unwise link
   * that downloaded a malicious DLL to the same directory under one
   * of various magic names that seem to be things that standard
   * Windows DLLs delegate to.
   *
   * It shouldn't break deliberate loading of user-provided DLLs
   * such as GSSAPI providers, because those are specified by their
   * full pathname by the user-provided configuration.
   */
  static HMODULE kernel32_module;
  DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD));

  if (!kernel32_module) {
    kernel32_module = load_system32_dll("kernel32.dll");
#if (defined _MSC_VER && _MSC_VER < 1900) || defined COVERITY
    /* For older Visual Studio, and also for the system I
     * currently use for Coveritying the Windows code, this
     * function isn't available in the header files to
     * type-check */
    GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module,
                                      SetDefaultDllDirectories);
#else
    GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
#endif
  }

  if (p_SetDefaultDllDirectories) {
    /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified
     * directories only */
    p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 |
                               LOAD_LIBRARY_SEARCH_USER_DIRS);
  }
}

BOOL init_winver(void)
{
  ZeroMemory(&osVersion, sizeof(osVersion));
  osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  return GetVersionEx((OSVERSIONINFO *)&osVersion);
}

HMODULE load_system32_dll(const char *libname)
{
  /*
   * Wrapper function to load a DLL out of c:\windows\system32
   * without going through the full DLL search path. (Hence no
   * attack is possible by placing a substitute DLL earlier on that
   * path.)
   */
  static char *sysdir = NULL;
  char *fullpath;
  HMODULE ret;

  if (!sysdir) {
    int size = 0, len;
    do {
      size = 3 * size / 2 + 512;
      sysdir = sresize(sysdir, size, char);
      len = GetSystemDirectory(sysdir, size);
    } while (len >= size);
  }

  fullpath = dupcat(sysdir, "\\", libname, NULL);
  ret = LoadLibrary(fullpath);
  sfree(fullpath);
  return ret;
}

/*
 * A tree234 containing mappings from system error codes to strings.
 */

struct errstring {
  int error;
  char *text;
};

static int errstring_find(void *av, void *bv)
{
  int *a = (int *)av;
  struct errstring *b = (struct errstring *)bv;
  if (*a < b->error)
    return -1;
  if (*a > b->error)
    return +1;
  return 0;
}
static int errstring_compare(void *av, void *bv)
{
  struct errstring *a = (struct errstring *)av;
  return errstring_find(&a->error, bv);
}

static tree234 *errstrings = NULL;

const char *win_strerror(int error)
{
  struct errstring *es;

  if (!errstrings)
    errstrings = newtree234(errstring_compare);

  es = find234(errstrings, &error, errstring_find);

  if (!es) {
    char msgtext[65536]; /* maximum size for FormatMessage is 64K */

    es = snew(struct errstring);
    es->error = error;
    if (!FormatMessage(
            (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS),
            NULL,
            error,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            msgtext,
            lenof(msgtext) - 1,
            NULL)) {
      sprintf(msgtext,
              "(unable to format: FormatMessage returned %u)",
              (unsigned int)GetLastError());
    } else {
      int len = strlen(msgtext);
      if (len > 0 && msgtext[len - 1] == '\n')
        msgtext[len - 1] = '\0';
    }
    es->text = dupprintf("Error %d: %s", error, msgtext);
    add234(errstrings, es);
  }

  return es->text;
}

#ifdef DEBUG
static FILE *debug_fp = NULL;
static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
static int debug_got_console = 0;

void dputs(const char *buf)
{
  DWORD dw;

  if (!debug_got_console) {
    if (AllocConsole()) {
      debug_got_console = 1;
      debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
    }
  }
  if (!debug_fp) {
    debug_fp = fopen("debug.log", "w");
  }

  if (debug_hdl != INVALID_HANDLE_VALUE) {
    WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
  }
  fputs(buf, debug_fp);
  fflush(debug_fp);
}
#endif

#ifdef MINEFIELD
/*
 * Minefield - a Windows equivalent for Electric Fence
 */

#define PAGESIZE 4096

/*
 * Design:
 *
 * We start by reserving as much virtual address space as Windows
 * will sensibly (or not sensibly) let us have. We flag it all as
 * invalid memory.
 *
 * Any allocation attempt is satisfied by committing one or more
 * pages, with an uncommitted page on either side. The returned
 * memory region is jammed up against the _end_ of the pages.
 *
 * Freeing anything causes instantaneous decommitment of the pages
 * involved, so stale pointers are caught as soon as possible.
 */

static int minefield_initialised = 0;
static void *minefield_region = NULL;
static long minefield_size = 0;
static long minefield_npages = 0;
static long minefield_curpos = 0;
static unsigned short *minefield_admin = NULL;
static void *minefield_pages = NULL;

static void minefield_admin_hide(int hide)
{
  int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
  VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
}

static void minefield_init(void)
{
  int size;
  int admin_size;
  int i;

  for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
    minefield_region = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);
    if (minefield_region)
      break;
  }
  minefield_size = size;

  /*
   * Firstly, allocate a section of that to be the admin block.
   * We'll need a two-byte field for each page.
   */
  minefield_admin = minefield_region;
  minefield_npages = minefield_size / PAGESIZE;
  admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
  minefield_npages = (minefield_size - admin_size) / PAGESIZE;
  minefield_pages = (char *)minefield_region + admin_size;

  /*
   * Commit the admin region.
   */
  VirtualAlloc(
      minefield_admin, minefield_npages * 2, MEM_COMMIT, PAGE_READWRITE);

  /*
   * Mark all pages as unused (0xFFFF).
   */
  for (i = 0; i < minefield_npages; i++)
    minefield_admin[i] = 0xFFFF;

  /*
   * Hide the admin region.
   */
  minefield_admin_hide(1);

  minefield_initialised = 1;
}

static void minefield_bomb(void)
{
  div(1, *(int *)minefield_pages);
}

static void *minefield_alloc(int size)
{
  int npages;
  int pos, lim, region_end, region_start;
  int start;
  int i;

  npages = (size + PAGESIZE - 1) / PAGESIZE;

  minefield_admin_hide(0);

  /*
   * Search from current position until we find a contiguous
   * bunch of npages+2 unused pages.
   */
  pos = minefield_curpos;
  lim = minefield_npages;
  while (1) {
    /* Skip over used pages. */
    while (pos < lim && minefield_admin[pos] != 0xFFFF)
      pos++;
    /* Count unused pages. */
    start = pos;
    while (pos < lim && pos - start < npages + 2 &&
           minefield_admin[pos] == 0xFFFF)
      pos++;
    if (pos - start == npages + 2)
      break;
    /* If we've reached the limit, reset the limit or stop. */
    if (pos >= lim) {
      if (lim == minefield_npages) {
        /* go round and start again at zero */
        lim = minefield_curpos;
        pos = 0;
      } else {
        minefield_admin_hide(1);
        return NULL;
      }
    }
  }

  minefield_curpos = pos - 1;

  /*
   * We have npages+2 unused pages starting at start. We leave
   * the first and last of these alone and use the rest.
   */
  region_end = (start + npages + 1) * PAGESIZE;
  region_start = region_end - size;
  /* FIXME: could align here if we wanted */

  /*
   * Update the admin region.
   */
  for (i = start + 2; i < start + npages + 1; i++)
    minefield_admin[i] = 0xFFFE; /* used but no region starts here */
  minefield_admin[start + 1] = region_start % PAGESIZE;

  minefield_admin_hide(1);

  VirtualAlloc(
      (char *)minefield_pages + region_start, size, MEM_COMMIT, PAGE_READWRITE);
  return (char *)minefield_pages + region_start;
}

static void minefield_free(void *ptr)
{
  int region_start, i, j;

  minefield_admin_hide(0);

  region_start = (char *)ptr - (char *)minefield_pages;
  i = region_start / PAGESIZE;
  if (i < 0 || i >= minefield_npages ||
      minefield_admin[i] != region_start % PAGESIZE)
    minefield_bomb();
  for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
    minefield_admin[j] = 0xFFFF;
  }

  VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);

  minefield_admin_hide(1);
}

static int minefield_get_size(void *ptr)
{
  int region_start, i, j;

  minefield_admin_hide(0);

  region_start = (char *)ptr - (char *)minefield_pages;
  i = region_start / PAGESIZE;
  if (i < 0 || i >= minefield_npages ||
      minefield_admin[i] != region_start % PAGESIZE)
    minefield_bomb();
  for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++)
    ;

  minefield_admin_hide(1);

  return j * PAGESIZE - region_start;
}

void *minefield_c_malloc(size_t size)
{
  if (!minefield_initialised)
    minefield_init();
  return minefield_alloc(size);
}

void minefield_c_free(void *p)
{
  if (!minefield_initialised)
    minefield_init();
  minefield_free(p);
}

/*
 * realloc _always_ moves the chunk, for rapid detection of code
 * that assumes it won't.
 */
void *minefield_c_realloc(void *p, size_t size)
{
  size_t oldsize;
  void *q;
  if (!minefield_initialised)
    minefield_init();
  q = minefield_alloc(size);
  oldsize = minefield_get_size(p);
  memcpy(q, p, (oldsize < size ? oldsize : size));
  minefield_free(p);
  return q;
}

#endif /* MINEFIELD */

FontSpec *fontspec_new(const char *name, int bold, int height, int charset)
{
  FontSpec *f = snew(FontSpec);
  f->name = dupstr(name);
  f->isbold = bold;
  f->height = height;
  f->charset = charset;
  return f;
}
FontSpec *fontspec_copy(const FontSpec *f)
{
  return fontspec_new(f->name, f->isbold, f->height, f->charset);
}
void fontspec_free(FontSpec *f)
{
  sfree(f->name);
  sfree(f);
}
int fontspec_serialise(FontSpec *f, void *vdata)
{
  char *data = (char *)vdata;
  int len = strlen(f->name) + 1; /* include trailing NUL */
  if (data) {
    strcpy(data, f->name);
    PUT_32BIT_MSB_FIRST(data + len, f->isbold);
    PUT_32BIT_MSB_FIRST(data + len + 4, f->height);
    PUT_32BIT_MSB_FIRST(data + len + 8, f->charset);
  }
  return len + 12; /* also include three 4-byte ints */
}
FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used)
{
  char *data = (char *)vdata;
  char *end;
  if (maxsize < 13)
    return NULL;
  end = memchr(data, '\0', maxsize - 12);
  if (!end)
    return NULL;
  end++;
  *used = end - data + 12;
  return fontspec_new(data,
                      GET_32BIT_MSB_FIRST(end),
                      GET_32BIT_MSB_FIRST(end + 4),
                      GET_32BIT_MSB_FIRST(end + 8));
}
